package yhao.infra.web.common.enumsmap;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import yhao.infra.web.common.enumsmap.annotation.Selectable;
import yhao.infra.web.common.enumsmap.annotation.config.EnableSelectable;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * 包扫描bean，支持多个包扫描和子包递归扫描
 *
 * @author wuweipeng
 * @author liwan
 * @date 2021/7/20 9:16
 */
public class ApplicationPackageScanner implements InitializingBean {

    private static final String RESOURCE_PATTERN = "/**/*.class";
    private static final String MAIN = "main";

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    // 指定哪些包需要被扫描，支持多个包"foo.a, foo.b"并对每个包都会递归搜索
    private String[] packagesToScan;

    // 指定扫描包中含有特定注解标记的类
    private Class<Selectable> annotationFilterClazz = Selectable.class;

    private TypeFilter typeFilter;


    @Override
    public void afterPropertiesSet() {
        Class mainClass = deduceMainApplicationClass();
        EnableSelectable enableSelectable = (EnableSelectable) mainClass.getAnnotation(EnableSelectable.class);
        this.packagesToScan=enableSelectable.packagesToScan();
        if (annotationFilterClazz != null ) {
            typeFilter = new AnnotationTypeFilter(annotationFilterClazz);
        }
    }

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if (MAIN.equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }


    /**
     * 扫描 package 获取所有符合过滤条件的 Class
     *
     * @return
     */
    public Set<Class<?>> scanPackages() {
        Set<Class<?>> matchClazz = new HashSet<>();
        try {
            if (this.packagesToScan != null && this.packagesToScan.length > 0) {
                for (String pkg : packagesToScan) {
                    String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                            + ClassUtils.convertClassNameToResourcePath(pkg)
                            + RESOURCE_PATTERN;
                    Resource[] resources = resourcePatternResolver.getResources(pattern);
                    MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
                    for (Resource resource : resources) {
                        if (resource.isReadable()) {
                            MetadataReader reader = readerFactory.getMetadataReader(resource);
                            String className = reader.getClassMetadata().getClassName();
                            if (matchesEntityTypeFilter(reader, readerFactory)) {
                                matchClazz.add(resourcePatternResolver.getClassLoader().loadClass(className));
                            }
                        }
                    }
                }
            }
        } catch (IOException ex) {
            throw new IllegalArgumentException("Failed to scan classpath for unlisted classes", ex);
        } catch (ClassNotFoundException ex) {
            throw new IllegalArgumentException("Failed to load annotated classes from classpath", ex);
        }
        return matchClazz;
    }


    /**
     * 检查当前扫描到的Bean含有任何一个指定的注解标记
     *
     * @param reader
     * @param readerFactory
     * @return
     * @throws IOException
     */
    private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
        if (typeFilter.match(reader, readerFactory)) {
            return true;
        }
        return false;
    }

}
